﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace HerbertDotNet.Visualizer
{
    public partial class MainForm : Form
    {
        protected delegate void UpdateBackgroundPathDelegate();
        protected delegate bool NextProgramStepDelegate();
        protected delegate void ProgramFinishedDelegate(bool stopped);

        protected const string _projectWebSiteUrl = "http://www.codeplex.com/herbertdotnet/";

        protected const int _maxExecutionSpeed = 12; // Maximum execution speed, as power of two.

        protected int _executionSpeed;          // Speed of execution, as a power of two.
        protected int _executionStepInterval;   // Interval between steps of execution, in milliseconds.
        protected int _executionStepsPerCycle;  // Number of steps to advance per cycle of execution.
        protected Thread _executionThread;      // Thread to execute program.
        protected bool _executionPaused;        // True if execution is currently paused.

        protected int _programLastScore;        // Last score achieved by current program.
        protected HParseInfo _programParseInfo; // Information returned by program parser.

        protected List<HerbertLevel> _levels;   // List of loaded levels.
        protected string[] _levelPrograms;      // Current programs for each level.
        protected string[] _levelBestPrograms;  // Programs that achieved best scores for each level.
        protected int[] _levelBestScores;       // Best scores achieved for each level.

        protected bool _editingCode;            // True if user is currently editing code.

        public MainForm()
        {
            InitializeComponent();

            //// Size form so that level viewer control is square.
            //Size tileSize = new Size(10, 10);
            //this.ClientSize = new Size(tileSize.Width * (HerbertLevel.TilesWidth + 1) + ???,
            //    tileSize.Height * (HerbertLevel.TilesHeight + 1) + ???);
        }

        protected void LoadSolutions()
        {
            try
            {
                using (StreamReader reader = new StreamReader(loadSolutionsFileDialog.FileName))
                {
                    string line;
                    int lineNumber = 0;
                    int curLevel = -1;
                    int curSection = -1;
                    StringBuilder programCode = null;

                    var interpreter = (HerbertInterpreter)levelViewer.Interpreter.Clone();
                    
                    while (true)
                    {
                        // Read next line from stream.
                        line = reader.ReadLine();
                        lineNumber++;

                        if (line == null) break;
                        line = line.Trim();

                        // Check if line marks start of new level definition.
                        if (line.Length >= 6 && line.Substring(0, 5).ToLower() == "level")
                        {
                            curLevel = int.Parse(line.Substring(5));

                            // Check if level exists in currently loaded set of levels.
                            if (curLevel < 0 || curLevel > _levels.Count - 1)
                            {
                                throw new IOException(string.Format("Error reading line {0}.\r\n"
                                    + "Invalid level number ({1}). The specified level does not exist in"
                                    + " the currently loaded set of levels.",
                                    lineNumber, curLevel));
                            }

                            curSection = -1;
                            programCode = new StringBuilder();

                            continue;
                        }

                        // Check if current position is within level definition.
                        if (curLevel != -1)
                        {
                            // Check if line marks start of new section.
                            if (line.ToLower() == "[section]")
                            {
                                curSection++;
                            }
                            // Check if line marks end of section within definition.
                            else if (line.ToLower() == "[/section]")
                            {
                                // Check current section.
                                switch (curSection)
                                {
                                    case 0:
                                        _levelPrograms[curLevel] = programCode.ToString();

                                        // Reset string builder for code.
                                        programCode = new StringBuilder();

                                        break;
                                    case 1:
                                        _levelBestPrograms[curLevel] = programCode.ToString();

                                        try
                                        {
                                            // Calculate score for current best level.
                                            interpreter.Program = HerbertProgram.Parse(
                                                _levelBestPrograms[curLevel]);
                                            interpreter.Level = _levels[curLevel];
                                            interpreter.Program.Compile();

                                            _levelBestScores[curLevel] = interpreter.Run();
                                        }
                                        catch(Exception)
                                        {
                                            _levelBestScores[curLevel] = 0;
                                        }

                                        // Reset string builder for code.
                                        programCode = new StringBuilder();
                                        
                                        break;
                                }
                            }
                            else
                            {
                                // Append text of line to program code.
                                programCode.Append((programCode.Length > 0 ? Environment.NewLine
                                    : string.Empty) + line);
                            }
                        }
                    }
                }

                UpdateCurrentLevel();

                // Set file name of dialog to save solutions to file name of loaded solutions.
                saveSolutionsFileDialog.FileName = loadSolutionsFileDialog.FileName;
            }
            catch (IOException exIo)
            {
                // Update text of label for general status with error message.
                generalStatusLabel.Text = "Error loading solutions";

                MessageBox.Show("Error loading solutions from file.\r\n\r\n" + exIo.Message,
                    Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        protected void SaveSolutions()
        {
            try
            {
                using (StreamWriter writer = new StreamWriter(saveSolutionsFileDialog.FileName))
                {
                    // Write solutions for each level to stream.
                    for (int level = 0; level < _levels.Count; level++)
                    {
                        writer.WriteLine("level{0}", level);

                        writer.WriteLine("[section]");
                        writer.WriteLine(_levelPrograms[level]);
                        writer.WriteLine("[/section]");

                        writer.WriteLine("[section]");
                        writer.WriteLine(_levelBestPrograms[level]);
                        writer.WriteLine("[/section]");

                        writer.WriteLine();
                    }
                }

                // Update text of label for general status.
                generalStatusLabel.Text = "Solutions saved";
            }
            catch (IOException exIo)
            {
                // Update text of label for general status with error message.
                generalStatusLabel.Text = "Error saving solutions";

                MessageBox.Show("Error saving solutions to file.\r\n\r\n" + exIo.Message,
                    Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        protected void LoadLevel()
        {
            try
            {
                // Reset list of levels and then add single level to it.
                _levels = new List<HerbertLevel>();
                _levels.Add(HerbertLevel.FromFile(loadLevelDialog.FileName));

                // Reset programs and scores for each level.
                _levelPrograms = new string[1];
                _levelBestPrograms = new string[1];
                _levelBestScores = new int[1];
                _programLastScore = 0;

                ResetControls();
            }
            catch (IOException exIo)
            {
                // Update text of label for general status with error message.
                generalStatusLabel.Text = "Error loading level from file";

                MessageBox.Show("Error loading level from file.\r\n\r\n" + exIo.Message,
                    Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        protected void LoadMatch()
        {
            try
            {
                // Reset list of levels and then add each level in selected folder to it.
                _levels = new List<HerbertLevel>();

                int levelIndex = 0;
                string curFileName;

                while (true)
                {
                    levelIndex++;
                    curFileName = Path.Combine(loadMatchDialog.SelectedPath,
                        string.Format("level{0}.txt", levelIndex));
                    if (!File.Exists(curFileName)) break;

                    _levels.Add(HerbertLevel.FromFile(curFileName));
                }

                if (_levels.Count == 0) throw new IOException(
                    "Cannot find any level files in the specified match directory.");

                // Reset programs and scores for each level.
                _levelPrograms = new string[_levels.Count];
                _levelBestPrograms = new string[_levels.Count];
                _levelBestScores = new int[_levels.Count];
                _programLastScore = 0;

                ResetControls();
            }
            catch (IOException exIo)
            {
                // Update text of label for general status with error message.
                generalStatusLabel.Text = "Error loading match levels from files";

                MessageBox.Show("Error loading levels of match from files.\r\n\r\n" + exIo.Message,
                    Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        protected void ResetLevel()
        {
            // Stop execution of program.
            programStopToolStripMenuItem.PerformClick();

            // Reset list of levels and add blank level.
            _levels = new List<HerbertLevel>();
            _levels.Add(new HerbertLevel()
            {
                StartPosition = new HPoint(HerbertLevel.TilesWidth / 2,
                    HerbertLevel.TilesHeight / 2)
            });

            // Reset programs and scores for each level.
            _levelPrograms = new string[1];
            _levelBestPrograms = new string[1];
            _levelBestScores = new int[1];
            _programLastScore = 0;

            // Reset all controls.
            loadLevelDialog.FileName = string.Empty;
            loadMatchDialog.SelectedPath = Properties.Settings.Default.DefaultMatchesPath;

            levelViewer.Level = _levels[0];
            levelViewer.Reset();

            ResetControls();
        }

        protected void ResetControls()
        {
            // Update level scroll bar.
            levelScrollBar.Maximum = _levels.Count - 1;
            levelScrollBar.Value = 0;

            // Update controls on info tool bar.
            totalScoreToolStripLabel.Visible = _levels.Count > 1;

            // Set that user is editing code by default.
            _editingCode = true;

            UpdateCurrentLevel();
        }

        protected void UpdateCurrentLevel()
        {
            // Update text on Info Panel.
            levelNameToolStripLabel.Text = _levels.Count == 1 ? loadLevelDialog.SafeFileName
                : string.Format("Level {0}/{1} ({2})", levelScrollBar.Value + 1, _levels.Count,
                loadMatchDialog.SelectedPath.Substring(loadMatchDialog.SelectedPath.LastIndexOf(
                    Path.DirectorySeparatorChar) + 1));
            if (levelNameToolStripLabel.Text == string.Empty)
                levelNameToolStripLabel.Text = "(empty level)";

            // Update level viewer control.
            levelViewer.Level = _levels[levelScrollBar.Value];

            // Update code and last score to those for current level.
            codeTextBox.Text = _levelPrograms[levelScrollBar.Value];
            _programLastScore = 0;

            UpdateProgram();
            UpdateProgramLength();
            UpdateScores();
        }

        protected void UpdateExecutionSpeed()
        {
            // Limit execution speed between min and max values.
            if (_executionSpeed < -_maxExecutionSpeed) _executionSpeed = -_maxExecutionSpeed;
            if (_executionSpeed > +_maxExecutionSpeed) _executionSpeed = +_maxExecutionSpeed;

            // Calculate interval between steps of program and number of steps per cycle.
            double speedMultiple = Math.Pow(2, _executionSpeed);
            double stepInterval = 50.0 / speedMultiple;

            _executionStepInterval = (int)stepInterval;
            _executionStepsPerCycle = stepInterval < 1.0 ? (int)(1.0 / stepInterval) : 1;

            // Update label for execution speed.
            executionSpeedToolStripLabel.Text = string.Format("Speed: {1}{0}", Math.Pow(2,
                Math.Abs(_executionSpeed)), _executionSpeed >= 0 ? "x" : "/");
        }

        protected void UpdateProgramControlButtons()
        {
            // Enable/disable buttons to step forward/backward through prorgam.
            programStepBackwardToolStripMenuItem.Enabled = levelViewer.CurrentRunStateIndex > 0;
            programStepForwardToolStripMenuItem.Enabled = levelViewer.CurrentRunStateIndex
                < levelViewer.RunStates.Count - 1 || !levelViewer.ProgramFinished;
        }

        protected void UpdateTrace()
        {
            // Do not update trace if user is editing code.
            if (_editingCode) return;

            if (viewTraceExecutionToolStripMenuItem.Checked)
            {
                // Check that level viewer can highlight the current run state.
                if ((levelViewer.CurrentRunStateIndex >= 0 && levelViewer.CurrentRunStateIndex
                    <= levelViewer.RunStates.Count - 1))
                {
                    if (levelViewer.CurrentRunState.ProgramCounter == levelViewer.Program.Code.Length)
                    {
                        // Move selection to end of text.
                        codeTextBox.SelectionStart = codeTextBox.TextLength;
                    }
                    else
                    {
                        var programCounter = levelViewer.CurrentRunState.ProgramCounter;

                        if (programCounter != -1)
                        {
                            // Highlight code in code text box for current run state.
                            int curCharIndex = _programParseInfo.InstructionCharIndices[programCounter];

                            codeTextBox.Select(curCharIndex, 1);
                        }
                    }
                }
                else
                {
                    codeTextBox.DeselectAll();
                }
            }
        }

        protected void UpdateBackgroundPath()
        {
            // Check whether to display background path.
            if (viewDisplayBackgroundPathToolStripMenuItem.Checked)
            {
                if (levelViewer.Program == null)
                {
                    // Clear background path.
                    levelViewer.BackgroundPath = null;
                }
                else
                {
                    // Clear background path until it has been regenerated.
                    levelViewer.BackgroundPath = null;

                    // Repaint level viewer.
                    this.Invoke(new MethodInvoker(levelViewer.Refresh));

                    IEnumerable<HProgramRunState?> runStates;

                    try
                    {
                        // Get runs states for current program.
                        runStates = this.levelViewer.Interpreter.GetProgramRunStates();

                        var lastState = new HProgramRunState()
                        {
                            Position = this.levelViewer.Level.StartPosition
                        };

                        // Get each position from list of run states from which to create path.
                        // Ignore position if it is same as last position.
                        var prevDupPoint = new HPoint(-1, -1);
                        var backgroundPathWithDups = (from state in runStates
                                                      select state.Value).Where(state =>
                                                      {
                                                          lastState = state;

                                                          if (state.Position == prevDupPoint) return false;
                                                          prevDupPoint = state.Position;

                                                          return true;
                                                      });

                        // Create new path from old path but with all duplicate connections removed. 
                        var backgroundPath = new List<HPoint>();
                        var connections = new List<PathConnection>();
                        var prevPoint = new HPoint(-1, -1);
                        PathConnection conn;
                        bool prevPointMissed = false;

                        foreach (HPoint point in from state in backgroundPathWithDups select state.Position)
                        {
                            // Check if connection between this and previous point has already been added.
                            conn = new PathConnection(prevPoint, point);

                            if (connections.Contains(conn))
                            {
                                prevPoint = point;
                                prevPointMissed = true;
                                continue;
                            }

                            // Add current connection to list of connection.
                            connections.Add(conn);

                            // Add previous point to path if it was missed on last iteration.
                            if (prevPointMissed)
                            {
                                prevPointMissed = false;
                                backgroundPath.Add(prevPoint);
                            }

                            // Add current point to path.
                            backgroundPath.Add(point);

                            // Set previous point.
                            prevPoint = point;
                        }

                        // Add start position of level to beginning of path.
                        backgroundPath.Insert(0, levelViewer.Level.StartPosition);

                        levelViewer.BackgroundPath = backgroundPath;
                        levelViewer.BackgroundPathLastState = lastState;
                    }
                    catch (HerbertRuntimeException)
                    {
                        levelViewer.BackgroundPath = null;
                    }
                    finally
                    {
                        runStates = null;

                        // Perform garbage collection on unused memory.
                        GC.Collect();
                    }
                }
            }
            else
            {
                levelViewer.BackgroundPath = null;
            }

            // Update last score achieved by program.
            _programLastScore = levelViewer.GetLastScore();

            UpdateScores();

            // Repaint level viewer.
            this.Invoke(new MethodInvoker(levelViewer.Refresh));
        }

        protected void UpdateProgramLength()
        {
            int progByteLength = levelViewer.Program == null ? 0 : levelViewer.Program.GetByteLength();

            // Update label for program length.
            programLengthStatusLabel.Text = string.Format("{0}/{1} bytes", progByteLength,
                levelViewer.Level.ProgramMaxByteLength);
        }

        protected void UpdateScores()
        {
            // Check if current program beats or equals previous best program for this level.
            if (_programLastScore >= _levelBestScores[levelScrollBar.Value])
            {
                _levelBestPrograms[levelScrollBar.Value] = codeTextBox.Text;
                _levelBestScores[levelScrollBar.Value] = _programLastScore;
            }

            // Update labels for scores on info tool bar.
            levelBestScoreToolStripLabel.Text = string.Format("Best Score: {0}/{1}", _levelBestScores[
                levelScrollBar.Value], levelViewer.Level.Points);

            if (totalScoreToolStripLabel.Visible)
                totalScoreToolStripLabel.Text = string.Format("Total Score: {0}/{1}", _levelBestScores.Sum(),
                    (from level in _levels select (int)level.Points).Sum());

            // Update label for current score on status bar.
            programScoreStatusLabel.Text = string.Format("Score: {0}/{1}", _programLastScore,
                levelViewer.Level.Points);
        }

        protected void UpdateProgram()
        {
            // Set that user is currently editing code.
            _editingCode = true;

            // Update label for general status.
            generalStatusLabel.Text = "Ready";

            // Reset error tool tip for status strip.
            errorToolTip.SetToolTip(statusStrip, null);

            try
            {
                // Parse program from text.
                var program = HerbertProgram.Parse(codeTextBox.Text, out _programParseInfo);

                // Compile this program.
                program.Compile();

                // Set program for level viewer to use.
                levelViewer.Program = program;
            }
            catch (HerbertParserException exParser)
            {
                levelViewer.Program = null;

                // Update label for general status with error message.
                generalStatusLabel.Text = "Error parsing program code";

                // Update error tool tip for status strip.
                errorToolTip.ToolTipTitle = generalStatusLabel.Text;
                errorToolTip.ToolTipIcon = ToolTipIcon.Error;
                errorToolTip.SetToolTip(statusStrip, exParser.Message + (exParser.InnerException
                    == null ? string.Empty : "\r\n(" + exParser.InnerException.Message + ")"));
            }
            catch (HerbertCompilerException exCompiler)
            {
                levelViewer.Program = null;

                // Update label for general status with error message.
                generalStatusLabel.Text = "Error compiling program code";

                // Update error tool tip for status strip.
                errorToolTip.ToolTipTitle = generalStatusLabel.Text;
                errorToolTip.ToolTipIcon = ToolTipIcon.Error;
                errorToolTip.SetToolTip(statusStrip, exCompiler.Message + (exCompiler.InnerException
                    == null ? string.Empty : "\r\n(" + exCompiler.InnerException.Message + ")"));
            }

            UpdateProgramLength();
            UpdateProgramControlButtons();

            var updateBackgroundPathDelegate = new UpdateBackgroundPathDelegate(UpdateBackgroundPath);
            updateBackgroundPathDelegate.BeginInvoke(null, null);
        }

        #region Program Execution

        protected void ExecuteProgram()
        {
            // Advance program step by step until it has terminated or been stopped.
            bool canMove = true;
            bool programStopped = false;

            try
            {
                while (canMove)
                {
                    // Wait for step interval.
                    Thread.Sleep(_executionStepInterval);

                    for (int i = 0; i < _executionStepsPerCycle; i++)
                    {
                        if (_executionPaused) continue;

                        // Move to next state.
                        canMove = (bool)this.Invoke(new NextProgramStepDelegate(
                            levelViewer.MoveToNextState));
                    }

                    // Redraw control.
                    this.Invoke(new MethodInvoker(levelViewer.Update));
                }
            }
            catch (ThreadAbortException)
            {
                // Set that program has been stopped, rather than ended by itself.
                programStopped = true;
            }
            finally
            {
                _executionPaused = false;

                try
                {
                    // Execution has finished.
                    this.Invoke(new ProgramFinishedDelegate(ExecutionFinished), programStopped
                        | levelViewer.RuntimeErrorOccurred);
                }
                catch (ObjectDisposedException) { }
            }
        }

        protected void ExecutionFinished(bool stopped)
        {
            // Update last score achieved by program.
            _programLastScore = levelViewer.GetLastScore();

            UpdateScores();

            // Configure controls for stopped state.
            programRunToolStripMenuItem.Enabled = true;
            programRunToolStripMenuItem.Visible = true;
            programPauseToolStripMenuItem.Enabled = false;
            programPauseToolStripMenuItem.Visible = false;
            programStopToolStripMenuItem.Enabled = false;
            codeTextBox.ReadOnly = false;

            // Set that user is currently not editing code.
            _editingCode = false;

            // Update text of general status label only if program ended by itself.
            if (!stopped) generalStatusLabel.Text = "Finished";
        }

        #endregion

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            return base.ProcessCmdKey(ref msg, keyData);
        }

        protected override bool ProcessDialogKey(Keys keyData)
        {
            // Check which keys are pressed.

            if (keyData == (Keys.Back | Keys.Control))
            {
                programRevertToBestToolStripMenuItem.PerformClick();
            }

            if (keyData == Keys.MediaPlayPause)
            {
                // Run/pause execution of program depending on current execution state.
                if (programRunToolStripMenuItem.Enabled)
                    programRunToolStripMenuItem.PerformClick();
                else
                    programPauseToolStripMenuItem.PerformClick();
            }

            if (keyData == Keys.MediaStop)
            {
                programStopToolStripMenuItem.PerformClick();
            }

            return base.ProcessDialogKey(keyData);
        }

        private void CheckForUpdatesCompleted(CheckForUpdatesCompletedEventArgs e,
            bool showNoUpdatesMessage)
        {
            // Compare current and latest versions of application.
            var tempVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
            var curVersion = new Version(tempVersion.Major, tempVersion.Minor, tempVersion.Build);

            if (e.LatestReleaseVersion > curVersion)
            {
                switch (MessageBox.Show(string.Format("There is an update available to download.\r\n"
                    + "Would you like to visit the web page to download this update now?\r\n"
                    + "\r\n"
                    + "Latest version: {0}\r\n"
                    + "Current version: {1}\r\n", e.LatestReleaseVersion.ToString(), curVersion.ToString()),
                    Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Information,
                    MessageBoxDefaultButton.Button1))
                {
                    case DialogResult.Yes:
                        WebHelper.LaunchUrl(e.LatestReleaseUrl);

                        break;
                }
            }
            else
            {
                if (showNoUpdatesMessage)
                {
                    MessageBox.Show(string.Format("There are no updates available to download.\r\n\r\n"
                        + "Current (latest) version: {0}\r\n", curVersion.ToString()),
                        Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            // Initialize settings to default.
            if (string.IsNullOrEmpty(Properties.Settings.Default.DefaultMatchesPath))
                Properties.Settings.Default.DefaultMatchesPath = Application.StartupPath;

            Properties.Settings.Default.Save();

            // Set maximum number of iterations for interpreter.
            levelViewer.Interpreter.MaxIterations = (int)Properties.Settings.Default.ExecutionMaxIterations;

            // Set default execution speed. Zero means that speed is standard speed.
            _executionSpeed = 0;
            _executionPaused = false;

            UpdateExecutionSpeed();

            // Show empty level by default.
            ResetLevel();

            // Check what command line arguments were specified.
            string[] args = Environment.GetCommandLineArgs();

#if !DEBUG
            // Note: first argument is simply file name of this executable.
            if (args.Length >= 2)
            {
                var pathArg = args[1];

                // Check if argument is directory or file.
                if (Directory.Exists(pathArg))
                {
                    // Load match from specified path.
                    loadMatchDialog.SelectedPath = pathArg;
                    LoadMatch();
                }
                else if (File.Exists(pathArg))
                {
                    // Load level from specified path.
                    loadLevelDialog.FileName = pathArg;
                    LoadLevel();
                }
            }
#endif

            if (Properties.Settings.Default.CheckForUpdatesOnStartup)
            {
                // Check for updates on project website.
                WebHelper.CheckForUpdatesCompleted += new EventHandler<CheckForUpdatesCompletedEventArgs>(
                    WebHelper_StartupCheckForUpdatesCompleted);
                WebHelper.CheckForUpdatesAsync(_projectWebSiteUrl);
            }

            // Select text box for code.
            codeTextBox.Select();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            // Check if more than one level is loaded.
            if (_levels.Count > 1)
            {
                // Ask user whether to save solutions.
                switch (MessageBox.Show("Would you like to save the solutions (current and best) for the"
                    + " loaded match before exiting?",
                    Application.ProductName, MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question,
                    MessageBoxDefaultButton.Button1))
                {
                    case DialogResult.Yes:
                        // Click button to save solutions.
                        levelSaveSolutionsToolStripMenuItem.PerformClick();

                        break;
                    case DialogResult.Cancel:
                        e.Cancel = true;

                        break;
                }
            }
        }

        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            // Abort thread that executes program, if running.
            if (_executionThread != null && _executionThread.IsAlive) _executionThread.Abort();

            // Save user settings.
            Properties.Settings.Default.MainFormTraceExecution = viewTraceExecutionToolStripMenuItem.Checked;
            Properties.Settings.Default.MainFormDisplayPath = viewDisplayBackgroundPathToolStripMenuItem.Checked;
            Properties.Settings.Default.MainFormShowToolBar = viewToolBarToolStripMenuItem.Checked;
            Properties.Settings.Default.MainFormShowStatusBar = viewStatusBarToolStripMenuItem.Checked;
            Properties.Settings.Default.Save();
        }

        #region Menu Strip

        #region Level Menu

        private void levelResetToolStripMenuItem_Click(object sender, EventArgs e)
        {
            ResetLevel();
        }

        private void levelLoadToolStripMenuItem_Click(object sender, EventArgs e)
        {
            switch (loadLevelDialog.ShowDialog())
            {
                case DialogResult.OK:
                    LoadLevel();
                    break;
            }
        }

        private void levelLoadMatchToolStripMenuItem_Click(object sender, EventArgs e)
        {
            switch (loadMatchDialog.ShowDialog())
            {
                case DialogResult.OK:
                    LoadMatch();
                    break;
            }
        }

        private void levelClearSolutionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Confirm action.
            switch (MessageBox.Show("Are you sure you want to clear all solutions for the currently"
                + " load levels?\r\n"
                + "The best solutions for each level will still be stored.",
                Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question,
                MessageBoxDefaultButton.Button2))
            {
                case DialogResult.Yes:
                    // Clear programs for each level.
                    for (int level = 0; level < _levels.Count; level++)
                    {
                        _levelPrograms[level] = null;
                    }

                    UpdateCurrentLevel();

                    break;
            }
        }

        private void levelLoadSolutionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            switch (loadSolutionsFileDialog.ShowDialog())
            {
                case DialogResult.OK:
                    LoadSolutions();

                    break;
            }
        }

        private void levelSaveSolutionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Check if file name of save solutions dialog is empty.
            if (saveSolutionsFileDialog.FileName == string.Empty)
            {
                levelSaveSolutionsAsToolStripMenuItem.PerformClick();
            }
            else
            {
                SaveSolutions();
            }
        }

        private void levelSaveSolutionsAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            switch (saveSolutionsFileDialog.ShowDialog())
            {
                case DialogResult.OK:
                    SaveSolutions();

                    break;
            }
        }

        #endregion

        #region Program Menu

        private void programRunToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_executionPaused)
            {
                // Unpause execution.
                _executionPaused = false;
            }
            else
            {
                // Check if program has finished executing.
                if (levelViewer.CurrentRunStateIndex == levelViewer.RunStates.Count - 1)
                    // Reset level viewer.
                    levelViewer.Reset();

                //if (levelViewer.ProgramFinished) return;

                // Start thread that executes progam.
                _executionThread = new Thread(new ThreadStart(ExecuteProgram));
                _executionThread.Start();
            }

            // Configure controls for running state.
            programRunToolStripMenuItem.Enabled = false;
            programRunToolStripMenuItem.Visible = false;
            programPauseToolStripMenuItem.Enabled = true;
            programPauseToolStripMenuItem.Visible = true;
            programStopToolStripMenuItem.Enabled = true;
            codeTextBox.ReadOnly = true;

            // Set that user is currently not editing code.
            _editingCode = false;

            generalStatusLabel.Text = "Running...";
        }

        private void programRunToolStripMenuItem_EnabledChanged(object sender, EventArgs e)
        {
            // Enable/disable corresponding button in tool strip.
            runToolStripButton.Enabled = ((ToolStripMenuItem)sender).Enabled;
        }

        private void programRunToolStripMenuItem_VisibleChanged(object sender, EventArgs e)
        {
            // Show/hide corresponding button in tool strip.
            // Note: the Enabled property indicates whether the menu item will actually be shown, whereas
            // the Visible property only indicates whether the menu item is currently displayed on screen.
            runToolStripButton.Visible = ((ToolStripMenuItem)sender).Enabled;
        }

        private void programPauseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Pause execution thread.
            _executionPaused = true;

            // Configure controls for paused state.
            programRunToolStripMenuItem.Enabled = true;
            programRunToolStripMenuItem.Visible = true;
            programPauseToolStripMenuItem.Enabled = false;
            programPauseToolStripMenuItem.Visible = false;

            generalStatusLabel.Text = "Paused";
        }

        private void programPauseToolStripMenuItem_EnabledChanged(object sender, EventArgs e)
        {
            // Enable/disable corresponding button in tool strip.
            pauseToolStripButton.Enabled = ((ToolStripMenuItem)sender).Enabled;
        }

        private void programPauseToolStripMenuItem_VisibleChanged(object sender, EventArgs e)
        {
            // Show/hide corresponding button in tool strip.
            // Note: the Enabled property indicates whether the menu item will actually be shown, whereas
            // the Visible property only indicates whether the menu item is currently displayed on screen.
            pauseToolStripButton.Visible = ((ToolStripMenuItem)sender).Enabled;
        }

        private void programStopToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Check if execution thread is alive.
            if (_executionThread != null && _executionThread.IsAlive)
                // Abort execution thread.
                _executionThread.Abort();
            else
                // Call that program has finished, just in case the thread was not aborted properly.
                ExecutionFinished(true);

            // Reset level viewer.
            levelViewer.Reset();

            // Update label for general status.
            generalStatusLabel.Text = "Ready";

            // Reset error tool tip for status strip.
            errorToolTip.SetToolTip(statusStrip, null);
        }

        private void programStopToolStripMenuItem_EnabledChanged(object sender, EventArgs e)
        {
            // Enable/disable corresponding button in tool strip.
            stopToolStripButton.Enabled = ((ToolStripMenuItem)sender).Enabled;
        }

        private void programStepBackwardToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Set that user is currently not editing code.
            _editingCode = false;

            // Check whether to move to first or previous state.
            if ((Control.ModifierKeys & Keys.Shift) != 0)
            {
                // Move to first state in program.
                while (levelViewer.MoveToPreviousState()) ;
            }
            else
            {
                // Move to previous state in program.
                levelViewer.MoveToPreviousState();
            }
        }

        private void programStepBackwardToolStripMenuItem_EnabledChanged(object sender, EventArgs e)
        {
            // Enable/disable corresponding button in tool strip.
            stepBackwardToolStripButton.Enabled = ((ToolStripMenuItem)sender).Enabled;
        }

        private void programStepForwardToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Set that user is currently not editing code.
            _editingCode = false;

            // Check whether to move to last or next state.
            if ((Control.ModifierKeys & Keys.Shift) != 0)
            {
                // Move to last state in program.
                while (levelViewer.MoveToNextState()) ;
            }
            else
            {
                // Move to next state in program.
                levelViewer.MoveToNextState();
            }
        }

        private void programStepForwardToolStripMenuItem_EnabledChanged(object sender, EventArgs e)
        {
            // Enable/disable corresponding button in tool strip.
            stepForwardToolStripButton.Enabled = ((ToolStripMenuItem)sender).Enabled;
        }

        private void programDecreaseSpeedToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if ((Control.ModifierKeys & Keys.Shift) != 0)
                // Set execution speed to standard/minimum.
                _executionSpeed = _executionSpeed > 0 ? 0 : -_maxExecutionSpeed;
            else
                // Decrement execution speed.
                _executionSpeed--;

            UpdateExecutionSpeed();
        }

        private void programDecreaseSpeedToolStripMenuItem_EnabledChanged(object sender, EventArgs e)
        {
            // Enable/disable corresponding button in tool strip.
            decreaseSpeedToolStripButton.Enabled = ((ToolStripMenuItem)sender).Enabled;
        }

        private void programIncreaseSpeedToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if ((Control.ModifierKeys & Keys.Shift) != 0)
                // Set execution speed to standard/minimum.
                _executionSpeed = _executionSpeed < 0 ? 0 : +_maxExecutionSpeed;
            else
                // Increment execution speed.
                _executionSpeed++;

            UpdateExecutionSpeed();
        }

        private void programIncreaseSpeedToolStripMenuItem_EnabledChanged(object sender, EventArgs e)
        {
            // Enable/disable corresponding button in tool strip.
            increaseSpeedToolStripButton.Enabled = ((ToolStripMenuItem)sender).Enabled;
        }

        private void programRevertToBestToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Check that best program for current level has been set.
            string levelBestProgram = _levelBestPrograms[levelScrollBar.Value];

            if (levelBestProgram != null)
            {
                // Revert code and last score to those for best program of this level.
                codeTextBox.Text = levelBestProgram;
                _programLastScore = _levelBestScores[levelScrollBar.Value];

                UpdateScores();
            }
        }

        private void programRevertAllToBestToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Confirm action.
            switch (MessageBox.Show("Are you sure you want to revert all levels to their previous best"
                + " programs?",
                Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question,
                MessageBoxDefaultButton.Button2))
            {
                case DialogResult.Yes:
                    // Revert each level to best program.
                    for (int i = 0; i < _levelBestPrograms.Length - 1; i++)
                    {
                        if (_levelBestPrograms[i] != null)
                            _levelPrograms[i] = _levelBestPrograms[i];
                    }

                    // Check that best program for current level has been set.
                    string levelBestProgram = _levelBestPrograms[levelScrollBar.Value];

                    if (levelBestProgram != null)
                    {
                        // Revert code and last score to those for best program of current level.
                        codeTextBox.Text = _levelBestPrograms[levelScrollBar.Value];
                        _programLastScore = _levelBestScores[levelScrollBar.Value];
                    }

                    UpdateScores();

                    break;
            }
        }

        #endregion

        #region View Menu

        private void viewTraceExecutionToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
        {
            // Set whether to trace execution.
            levelViewer.Interpreter.TraceExecution = viewTraceExecutionToolStripMenuItem.Checked;
            
            // Reset level viewer.
            levelViewer.Reset();

            // Repaint level viewer.
            levelViewer.Refresh();

            // Update trace highlighting.
            UpdateTrace();
        }

        private void viewDisplayBackgroundPathToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
        {
            var updateBackgroundPathDelegate = new UpdateBackgroundPathDelegate(UpdateBackgroundPath);
            updateBackgroundPathDelegate.BeginInvoke(null, null);
        }

        private void viewToolBarToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
        {
            programToolStrip.Visible = viewToolBarToolStripMenuItem.Checked;

            // Update bounds of code text box according to visibility of tool strip.
            codeTextBox.SuspendLayout();
            codeTextBox.Top = programToolStrip.Visible ? programToolStrip.Bottom : 0;
            codeTextBox.Height = programToolStrip.Parent.ClientRectangle.Bottom - codeTextBox.Top;
            codeTextBox.ResumeLayout();
        }

        private void viewStatusBarToolStripMenuItem_CheckedChanged(object sender, EventArgs e)
        {
            statusStrip.Visible = viewStatusBarToolStripMenuItem.Checked;

            // Update bounds of split container according to visibility of status strip.
            splitContainer.Height = statusStrip.Parent.ClientRectangle.Bottom - splitContainer.Top
                - (statusStrip.Visible ? statusStrip.Height : 0);
        }

        #endregion

        #region Help Menu

        private void helpGettingStartedToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Launch web page for Getting Started.
            WebHelper.LaunchUrl(_projectWebSiteUrl + "Wiki/View.aspx?title=Getting%20Started");
        }

        private void helpForumToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Launch web page for Forum.
            WebHelper.LaunchUrl(_projectWebSiteUrl + "Thread/List.aspx");
        }

        private void helpSendFeedbackToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string sendFeedbackMessage = string.Format(
                "To send feedback to the developers about this program of report any bug that you have"
                + " found, please visit:\r\n"
                + "{0}\r\n"
                + "and make a comment on the appropiate release page or create a new discussion thread.",
                _projectWebSiteUrl);

            MessageBox.Show(sendFeedbackMessage, Application.ProductName, MessageBoxButtons.OK,
                MessageBoxIcon.Information);
        }

        private void helpCheckForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Check for updates on project website.
            WebHelper.CheckForUpdatesAsync(_projectWebSiteUrl);
        }

        private void helpAboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (var aboutDialog = new AboutBox())
            {
                aboutDialog.ShowDialog();
            }
        }

        private void WebHelper_StartupCheckForUpdatesCompleted(object sender, CheckForUpdatesCompletedEventArgs e)
        {
            CheckForUpdatesCompleted(e, false);

            // Remove handler for startup check for updates.
            WebHelper.CheckForUpdatesCompleted -= new EventHandler<CheckForUpdatesCompletedEventArgs>(
                WebHelper_StartupCheckForUpdatesCompleted);

            // Add handler to detect when any user-initiated check for updates has completed.
            WebHelper.CheckForUpdatesCompleted += new EventHandler<CheckForUpdatesCompletedEventArgs>(
                WebHelper_CheckForUpdatesCompleted);
        }

        private void WebHelper_CheckForUpdatesCompleted(object sender, CheckForUpdatesCompletedEventArgs e)
        {
            CheckForUpdatesCompleted(e, true);
        }

        #endregion

        private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            loadMatchDialog.SelectedPath = Properties.Settings.Default.DefaultMatchesPath; using (var settingsDialog = new SettingsDialog())
            {
                switch (settingsDialog.ShowDialog())
                {
                    case DialogResult.OK:
                        // Save settings.
                        Properties.Settings.Default.Save();

                        // Update everything with new settings.
                        loadMatchDialog.SelectedPath = Properties.Settings.Default.DefaultMatchesPath;
                        levelViewer.Interpreter.MaxIterations =
                            (int)Properties.Settings.Default.ExecutionMaxIterations;

                        break;
                    case DialogResult.Cancel:
                        // Reload settings, ignoring any changes made since first load.
                        Properties.Settings.Default.Reload();

                        break;
                }
            }
        }

        #endregion

        #region "Level Context Menu Strip"

        private void levelCopyCurrentViewToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Copy current image of level viewer to clipboard.
            using (var image = new Bitmap(levelViewer.ClientSize.Width, levelViewer.ClientSize.Height))
            {
                levelViewer.DrawToBitmap(image, levelViewer.ClientRectangle);
                Clipboard.SetImage(image);
            }
        }

        private void levelCopyLevelImageToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Copy current image of level viewer to clipboard.
            using (var image = new Bitmap(levelViewer.ClientSize.Width, levelViewer.ClientSize.Height))
            {
                levelViewer.DrawLevelToBitmap(image, levelViewer.ClientRectangle);
                Clipboard.SetImage(image);
            }
        }

        #endregion

        private void splitContainer_SplitterMoved(object sender, SplitterEventArgs e)
        {
            // Defocus splitter.
            splitContainer.Select();
        }

        private void splitContainer_DoubleClick(object sender, EventArgs e)
        {
            // Adjust position of splitter so that level viewer has equal width and height.
            splitContainer.SplitterDistance = levelViewer.Top + levelViewer.Width;
        }

        private void levelScrollBar_ValueChanged(object sender, EventArgs e)
        {
            // Stop execution of current program.
            programStopToolStripMenuItem.PerformClick();

            // Reset score for last program.
            _programLastScore = 0;

            // Reset background path for level viewer until it can be calculated for new level.
            levelViewer.BackgroundPath = null;

            UpdateCurrentLevel();
        }

        private void levelScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            //
        }

        private void levelViewer_MouseWheel(object sender, MouseEventArgs e)
        {
            // Adjust value of level scroll bar.
            levelScrollBar.Value = Math.Min(Math.Max(levelScrollBar.Value - e.Delta / 120,
                levelScrollBar.Minimum), levelScrollBar.Maximum);
        }

        private void levelViewer_RuntimeError(object sender, RuntimeErrorEventArgs e)
        {
            // Update label for general status with error message.
            generalStatusLabel.Text = "Error executing program";

            // Update error tool tip for status strip.
            errorToolTip.ToolTipTitle = "Error executing program";
            errorToolTip.ToolTipIcon = ToolTipIcon.Error;
            errorToolTip.SetToolTip(statusStrip, e.Error.Message + (e.Error.InnerException == null ?
                string.Empty : "\r\n(" + e.Error.InnerException.Message + ")"));
        }

        private void levelViewer_CurrentRunStateChanged(object sender, EventArgs e)
        {
            UpdateProgramControlButtons();
            UpdateTrace();
        }

        #region Tool Strip

        private void runToolStripButton_Click(object sender, EventArgs e)
        {
            programRunToolStripMenuItem.PerformClick();
        }

        private void pauseToolStripButton_Click(object sender, EventArgs e)
        {
            programPauseToolStripMenuItem.PerformClick();
        }

        private void stopToolStripButton_Click(object sender, EventArgs e)
        {
            programStopToolStripMenuItem.PerformClick();
        }

        private void stepBackwardToolStripButton_Click(object sender, EventArgs e)
        {
            programStepBackwardToolStripMenuItem.PerformClick();
        }

        private void stepForwardToolStripButton_Click(object sender, EventArgs e)
        {
            programStepForwardToolStripMenuItem.PerformClick();
        }

        private void decreaseSpeedToolStripButton_Click(object sender, EventArgs e)
        {
            programDecreaseSpeedToolStripMenuItem.PerformClick();
        }

        private void increaseSpeedToolStripButton_Click(object sender, EventArgs e)
        {
            programIncreaseSpeedToolStripMenuItem.PerformClick();
        }

        private void revertToBestProgramtoolStripButton_Click(object sender, EventArgs e)
        {
            programRevertToBestToolStripMenuItem.PerformClick();
        }

        private void revertAllToBestProgramtoolStripButton_Click(object sender, EventArgs e)
        {
            programRevertAllToBestToolStripMenuItem.PerformClick();
        }

        #endregion

        private void codeTextBox_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.A:
                    if (e.Control)
                    {
                        // Suppress key press event.
                        e.Handled = true;
                        e.SuppressKeyPress = true;

                        // Select all text.
                        codeTextBox.SelectAll();
                    }

                    break;
                case Keys.Left:
                case Keys.Right:
                    if (e.Control)
                    {
                        // Suppress key press event.
                        e.Handled = true;
                        e.SuppressKeyPress = true;
                    }

                    break;
            }
        }

        private void codeTextBox_KeyPress(object sender, KeyPressEventArgs e)
        {
            // Suppress DEL char.
            if (e.KeyChar == Convert.ToChar(127))
                e.Handled = true;
        }

        private void codeTextBox_TextChanged(object sender, EventArgs e)
        {
            // Store code for current program.
            _levelPrograms[levelScrollBar.Value] = codeTextBox.Text;

            // Update program from new code.
            UpdateProgram();
        }

        private void codeSelectionTimer_Tick(object sender, EventArgs e)
        {
            // Update label for current char within code text box.
            charToolStripStatusLabel.Text = string.Format("Ch {0}", codeTextBox.SelectionStart);
        }

        protected struct PathConnection
        {
            public HPoint Point1;
            public HPoint Point2;

            public static bool operator ==(PathConnection a, PathConnection b)
            {
                return a.Point1 == b.Point1 && a.Point2 == b.Point2;
            }

            public static bool operator !=(PathConnection a, PathConnection b)
            {
                return !(a == b);
            }

            public PathConnection(HPoint point1, HPoint point2)
            {
                this.Point1 = point1;
                this.Point2 = point2;
            }

            public override bool Equals(object obj)
            {
                var conn = (PathConnection)obj;

                return (Point1 == conn.Point1 && Point2 == conn.Point2)
                    || (Point1 == conn.Point2 && Point2 == conn.Point1);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }
    }
}
